package com.area42games.grammar.trees;

import java.util.ArrayList;
import java.util.Collections;

import org.antlr.runtime.Token;

import com.area42games.grammar.DiceLexer;

public class DiceTreeDice extends DiceTree 
{
	public DiceTreeDice(Token payload, EvalContext context) 
	{
		super(payload, context);
	}

	public int eval(boolean redo) throws InvalidReferenceException
	{
		if(redo)
		{
			computed = false;
			explanations = new ArrayList<String>();
		}

		if(computed)
		{
			return value;
		}

		// Need to check for negative numDice and negative dieSize
		int numDice = getDChild(0).eval(redo);
		if(numDice < 0)
		{
			numDice = 0;
		}
		int dieSize = getDChild(1).eval(redo);
		int dieTotal = 0;

		int high = -1;
		int low = -1;
		int rerollMin = -1;
		int rerollMax = -1;
		int rollagainMin = -1;
		int rollagainMax = -1;
		DiceTree lookupTree = null;

		if(children.size() > 2)
		{
			for(int j = 2; j < children.size(); j++)
			{
				DiceTree optChild = getDChild(j);
				switch(optChild.getType())
				{
				case DiceLexer.HIGH :
					if(low == -1)
					{
						high = optChild.getDChild(0).eval(redo);
					}
					break;
				case DiceLexer.LOW :
					if(high == -1)
					{
						low = optChild.getDChild(0).eval(redo);
					}
					break;
				case DiceLexer.REROLL :
					DiceTree child0 = optChild.getDChild(0);
					if(child0.getType() == DiceLexer.DOT)
					{
						rerollMin = child0.getDChild(0).eval(redo);
						rerollMax = child0.getDChild(1).eval(redo);
					}
					else
					{
						rerollMin = optChild.getDChild(0).eval(redo);
						rerollMax = rerollMin;
					}
					break;
				case DiceLexer.ROLLAGAIN :		
					DiceTree child1 = optChild.getDChild(0);
					if(child1.getType() == DiceLexer.DOT)
					{
						rollagainMin = child1.getDChild(0).eval(redo);
						rollagainMax = child1.getDChild(1).eval(redo);
					}
					else
					{
						rollagainMin = optChild.getDChild(0).eval(redo);
						rollagainMax = rollagainMin;
					}
					break;
				case DiceLexer.LBRACE :
					lookupTree = optChild;
					break;
				}
			}
		}

		ArrayList<Integer> rawDice = new ArrayList<Integer>();

		int rollAgainLimit = numDice * 10;
		for(int i = 0; i < numDice && rollAgainLimit > 0; i++)
		{			
			int intermediateValue = ((int)Math.floor(Math.random()*dieSize))+1;

			// Roll again -- potential for infinite loop, break it at a statistically ridiculous level.
			if(intermediateValue >= rollagainMin && intermediateValue <= rollagainMax)
			{
				numDice++;
				rollAgainLimit--;
			}
			
			// Reroll
			if(intermediateValue >= rerollMin && intermediateValue <= rerollMax)
			{
				i--;
				rollAgainLimit--;
			}
			else
			{
				rawDice.add(intermediateValue);
			}
		}

		if(	(high != -1 && high < numDice) || 
				(low  != -1 && low  < numDice)    )
		{
			Collections.sort(rawDice);
		}

		String explanation = getDChild(0).explain()+token.getText()+getDChild(1).explain();

		if(high > -1)
		{
			explanation += " high "+high;
		}
		if(low > -1)
		{
			explanation += " low "+low;
		}
		if(rerollMin > -1)
		{
			explanation += " reroll "+rerollMin;
			if(rerollMax != rerollMin)
			{
				explanation += ".."+rerollMax;				
			}
		}
		if(rollagainMin > -1)
		{
			explanation += " rollagain "+rollagainMin;
			if(rollagainMax != rollagainMin)
			{
				explanation += ".."+rollagainMax;				
			}
		}
		if(lookupTree != null)
		{
			explanation += " " + lookupTree.toString();
		}

		explanation += " [";

		// Only add the high or low dice based on the requirements.  Only one mode, low or high, can be active at once.
		if(low > 0 && low < numDice)
		{
			for(int pos = 0; pos < low; pos++)
			{
				int in = rawDice.get(pos);
				explanation += in + ",";
				dieTotal += lookup(in,lookupTree);
			}
			explanation += " (";
			for(int pos = low; pos < rawDice.size(); pos++)
			{
				int in = rawDice.get(pos);
				explanation += in + ",";				
			}
			explanation = explanation.substring(0,explanation.length()-1)+") ]";
		}
		else if(high > 0 && high < numDice)
		{
			for(int pos = numDice-high; pos < rawDice.size(); pos++)
			{
				int in = rawDice.get(pos);
				explanation += in + ",";
				dieTotal += lookup(in,lookupTree);
			}			
			explanation += " (";
			for(int pos = 0; pos < numDice-high; pos++)
			{
				int in = rawDice.get(pos);
				explanation += in + ",";				
			}
			explanation = explanation.substring(0,explanation.length()-1)+") ]";
		}
		else
		{
			for(int pos = 0; pos < rawDice.size(); pos++)
			{
				int in = rawDice.get(pos);
				explanation += in + ",";
				dieTotal += lookup(in,lookupTree);
			}
			explanation = explanation.substring(0,explanation.length()-1)+"]";
		}

		explanations.add(explanation);

		value = dieTotal;

		computed = true;
		return value;
	}
	
	private int lookup(int input, DiceTree lookupTree) throws InvalidReferenceException
	{
		if(lookupTree != null)
		{
			context.push(input);
			int retval = lookupTree.eval(true);
			//explanations.add(input +"="+ retval);
			context.pop();
			
			return retval;
		}
		
		return input;
	}
}
